/**
 *
 * Copyright (c) 2014 CoderKiss
 *
 * CoderKiss[AT]gmail.com
 *
 */

package me.dawson.kisstools.utils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.ExifInterface;
import android.media.ThumbnailUtils;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.MediaStore.Images;
import android.text.TextUtils;
import android.view.Gravity;

public class BitmapUtil {
	public static final String TAG = "BitmapUtil";

	public static ImageSize getImageSize(String absPath) {
		if (TextUtils.isEmpty(absPath)) {
			return null;
		}
		Options options = getOptions(absPath);
		int width = options.outWidth;
		int height = options.outHeight;
		ImageSize size = new ImageSize(width, height);
		return size;
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
	public static int getImageBytes(Bitmap bitmap) {
		if (bitmap == null) {
			return 0;
		}
		int bitmapSize = 0;
		if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
			bitmapSize = bitmap.getByteCount();
		} else {
			bitmapSize = bitmap.getRowBytes() * bitmap.getHeight();
		}
		if (bitmapSize < 0) {
			bitmapSize = 0;
		}

		return bitmapSize;
	}

	public static Options getOptions(String absPath) {
		if (TextUtils.isEmpty(absPath)) {
			return null;
		}
		Options options = new Options();
		options.inPreferredConfig = Config.ALPHA_8;
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(absPath, options);
		return options;
	}

	public static Options getOptions(String absPath, int desireWidth,
			int desireHeight) {
		if (!FileUtil.exists(absPath) || desireWidth <= 0 || desireHeight <= 0) {
			return null;
		}
		Options options = getOptions(absPath);
		int scaleW = sampleSize(options.outWidth, desireWidth);
		int scaleH = sampleSize(options.outHeight, desireWidth);
		options.inSampleSize = scaleH > scaleW ? scaleH : scaleW;
		options.inJustDecodeBounds = false;
		return options;
	}

	public static Bitmap getAssetBitmap(Context context, String filePath) {
		AssetManager assetManager = context.getAssets();
		InputStream ips = null;
		Bitmap bitmap = null;
		try {
			ips = assetManager.open(filePath);
			bitmap = BitmapFactory.decodeStream(ips);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return bitmap;
	}

	public static Bitmap getImage(String absPath) {
		if (!FileUtil.exists(absPath)) {
			LogUtil.w(TAG, "invalid file path: " + absPath);
			return null;
		}

		LogUtil.d(TAG, "decode bitmap " + absPath);
		long enter = System.currentTimeMillis();
		Bitmap bitmap = BitmapFactory.decodeFile(absPath);
		long delta = System.currentTimeMillis() - enter;
		LogUtil.v(TAG, "decode bitmap time " + delta);

		return bitmap;
	}

	public static Bitmap getImage(String absPath, ImageSize size) {
		if (TextUtils.isEmpty(absPath)) {
			return null;
		}

		if (ImageSize.valid(size)) {
			return getImage(absPath, size.getWidth(), size.getHeight());
		} else {
			return getImage(absPath);
		}
	}

	public static Bitmap getImage(String absPath, int width, int height) {
		if (width <= 0 && height <= 0 || !FileUtil.exists(absPath)) {
			LogUtil.e(TAG, "invalid parameters absPath " + absPath + " width "
					+ width + " height " + height);
			return null;
		}

		LogUtil.d(TAG, "decode bitmap " + absPath + " width " + width
				+ " height " + height);
		long enter = System.currentTimeMillis();
		Options options = getOptions(absPath, width, height);
		Bitmap bitmap = BitmapFactory.decodeFile(absPath, options);
		if (bitmap == null) {
			LogUtil.i(TAG, "decode bitmap failed " + absPath);
			return null;
		}

		int rotate = getRotate(absPath);
		bitmap = rotateImage(bitmap, rotate);
		System.gc();
		long delta = System.currentTimeMillis() - enter;
		LogUtil.v(TAG, "decode bitmap time " + delta);
		return bitmap;
	}

	public static Bitmap rotateImage(Bitmap bitmap, int rotate) {
		if (bitmap == null) {
			return bitmap;
		}
		Matrix matrix = new Matrix();
		matrix.setRotate(rotate);
		Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
				bitmap.getHeight(), matrix, true);
		if (rotated != null && !rotated.equals(bitmap)) {
			bitmap.recycle();
			bitmap = rotated;
		}
		return bitmap;
	}

	public static boolean saveImage(Bitmap bitmap, String absPath) {
		return saveBitmap(bitmap, absPath, 100);
	}

	public static boolean saveBitmap(Bitmap bitmap, String absPath, int quality) {
		if (!FileUtil.create(absPath)) {
			LogUtil.w(TAG, "create file failed.");
			return false;
		}

		try {
			File outFile = new File(absPath);
			FileOutputStream fos = new FileOutputStream(outFile);
			BufferedOutputStream bos = new BufferedOutputStream(fos);
			bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos);
			bos.flush();
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
			LogUtil.e(TAG, "failed to write image content");
			return false;
		}

		return true;
	}

	public static Bitmap squareImage(Bitmap bitmap) {
		if (bitmap == null) {
			return null;
		}
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		if (width == height) {
			LogUtil.w(TAG, "no need to square image");
			return bitmap;
		}
		int x = (height < width) ? ((width - height) / 2) : 0;
		int y = (width < height) ? ((height - width) / 2) : 0;
		int pixels = width < height ? width : height;
		Bitmap square = Bitmap.createBitmap(bitmap, x, y, pixels, pixels);
		return square;
	}

	public static int getRotate(String absPath) {
		if (!FileUtil.exists(absPath)) {
			LogUtil.w(TAG, "invalid file path");
			return 0;
		}

		ExifInterface exifInterface = null;
		try {
			exifInterface = new ExifInterface(absPath);
		} catch (IOException e) {
			e.printStackTrace();
			return 0;
		}

		int orientation = exifInterface.getAttributeInt(
				ExifInterface.TAG_ORIENTATION,
				ExifInterface.ORIENTATION_UNDEFINED);
		int rotate = 0;
		switch (orientation) {
		case ExifInterface.ORIENTATION_ROTATE_90:
			rotate = 90;
			break;
		case ExifInterface.ORIENTATION_ROTATE_180:
			rotate = 180;
			break;
		case ExifInterface.ORIENTATION_ROTATE_270:
			rotate = 270;
			break;
		default:
			break;
		}
		LogUtil.v(TAG, "image rotate " + rotate);
		return rotate;
	}

	public static Bitmap resizeImage(Bitmap bitmap, int desireWidth,
			int desireHeight, boolean isEnlarge) {
		if (bitmap == null || desireHeight <= 0 || desireWidth <= 0) {
			return null;
		}

		int width = bitmap.getWidth();
		int height = bitmap.getHeight();

		if (width <= 0 || height <= 0) {
			return bitmap;
		}
		if (!isEnlarge && (width < desireWidth && height < desireHeight)) {
			return bitmap;
		}
		float scale;
		if (width < height) {
			scale = (float) desireHeight / (float) height;
			if (desireWidth < width * scale) {
				scale = (float) desireWidth / (float) width;
			}
		} else {
			scale = (float) desireWidth / (float) width;
		}

		Matrix matrix = new Matrix();
		matrix.postScale(scale, scale);
		bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);

		return bitmap;
	}

	public final static Bitmap getThumbnail(String absPath, int width,
			int height) {
		if (!FileUtil.exists(absPath) || width <= 0 || height <= 0) {
			return null;
		}
		Options options = getOptions(absPath, width, height);
		try {
			Bitmap bitmap = BitmapFactory.decodeFile(absPath, options);
			Bitmap result = ThumbnailUtils.extractThumbnail(bitmap, width,
					height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
			// recycle(bitmap);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} catch (OutOfMemoryError e) {
			e.printStackTrace();
		}
		return null;
	}

	public final static Bitmap getVideoThumbnail(String absPath, int width,
			int height) {
		if (!FileUtil.exists(absPath) || width <= 0 || height <= 0) {
			return null;
		}
		Bitmap bitmap = null;
		bitmap = ThumbnailUtils.createVideoThumbnail(absPath,
				Images.Thumbnails.MINI_KIND);
		LogUtil.d(TAG, "bitmap width " + bitmap.getWidth() + " height "
				+ bitmap.getHeight());
		bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height,
				ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
		return bitmap;
	}

	public final static Bitmap waterMark(Context context, Bitmap bitmap,
			int waterResId, int gravity) {
		Drawable waterPrintDrawable = context.getResources().getDrawable(
				waterResId);
		int waterPrintWidth = waterPrintDrawable.getIntrinsicWidth();
		int waterPrintHeight = waterPrintDrawable.getIntrinsicHeight();
		int w = bitmap.getWidth();
		int h = bitmap.getHeight();
		if (gravity == Gravity.CENTER) {
			waterPrintDrawable.setBounds((w - waterPrintWidth) / 2,
					(h - waterPrintHeight) / 2, (w + waterPrintWidth) / 2,
					(h + waterPrintHeight) / 2);
		} else {
			waterPrintDrawable.setBounds((w - waterPrintWidth) / 2, h,
					(w + waterPrintWidth) / 2, h + waterPrintHeight);
		}
		Canvas mCanvas = new Canvas(bitmap);
		waterPrintDrawable.draw(mCanvas);
		return bitmap;
	}

	public final static Bitmap toRounded(Context context, Bitmap bitmap,
			int color, int borderDips, int desireWidth) {
		try {
			Bitmap bitmap0 = squareImage(bitmap);
			Bitmap bitmap1 = resizeImage(bitmap0, desireWidth, desireWidth,
					true);
			int toPX = DeviceUtil.dp2px(borderDips);
			int maxBorder = (desireWidth / 2) / 5;
			maxBorder = maxBorder > 15 ? 15 : maxBorder;
			final int borderSizePx = toPX > maxBorder ? maxBorder : toPX;

			final int size = desireWidth;
			int center = (int) (size / 2);
			int left = (int) ((desireWidth - size) / 2);
			int top = (int) ((desireWidth - size) / 2);
			int right = left + size;
			int bottom = top + size;

			Bitmap output = Bitmap.createBitmap(size, size, Config.ARGB_8888);
			Canvas canvas = new Canvas(output);
			final Paint paint = new Paint();

			final Rect src = new Rect(left, top, right, bottom);
			final Rect dst = new Rect(0, 0, size, size);

			canvas.drawARGB(0, 0, 0, 0);
			// draw border
			paint.setColor(Color.WHITE);
			paint.setAntiAlias(true);
			int radius = center - borderSizePx;
			canvas.drawCircle(center, center, radius, paint);
			// draw bitmap
			paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
			canvas.drawBitmap(bitmap1, src, dst, paint);
			paint.setXfermode(null);

			paint.setStyle(Paint.Style.STROKE);
			paint.setStrokeWidth((float) borderSizePx);
			radius = center - borderSizePx / 2;
			canvas.drawCircle(center, center, radius, paint);
			canvas.save();
			recycle(bitmap0, bitmap1, bitmap);
			return output;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bitmap;
	}

	public static Bitmap toRoundCorner(Context context, Bitmap bitmap) {
		try {
			int w = bitmap.getWidth();
			int h = bitmap.getHeight();
			final int delta = DeviceUtil.dp2px(5);
			final float roundPx = DeviceUtil.dp2px(14);
			final Paint paint = new Paint();
			paint.setAntiAlias(true);

			/* draw round foreground */
			int foreW = w - 2 * delta;
			int foreH = h - 2 * delta;
			Bitmap foreBmp = Bitmap
					.createBitmap(foreW, foreH, Config.ARGB_8888);
			Rect rect = new Rect(0, 0, foreW, foreH);
			RectF rectF = new RectF(rect);
			Canvas canvas0 = new Canvas(foreBmp);
			canvas0.drawRoundRect(rectF, roundPx, roundPx, paint);
			paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
			canvas0.drawBitmap(bitmap, null, rect, paint);

			/* draw round background */
			Drawable drawable = getDrawbale(0xffffffff, (int) (delta + roundPx));
			drawable.setBounds(0, 0, w, h);
			Bitmap result = Bitmap.createBitmap(w, h, Config.ARGB_8888);
			Canvas canvas = new Canvas(result);
			drawable.draw(canvas);
			canvas.drawBitmap(foreBmp, delta, delta, null);
			recycle(foreBmp, bitmap);

			return result;
		} catch (Exception e) {
			e.printStackTrace();
			return bitmap;
		}
	}

	public final static GradientDrawable getDrawbale(int argb, int radius) {
		GradientDrawable drawable = new GradientDrawable();
		drawable.setCornerRadius(radius);
		drawable.setColor(argb);
		return drawable;
	}

	public final static Bitmap drawRing(Bitmap bitmap, int deltaRadius,
			int color) {
		int w = bitmap.getWidth();
		int dia = deltaRadius * 2 + w;
		float radius = (float) dia / 2;
		Bitmap resultBitmap = Bitmap.createBitmap(dia, dia, Config.ARGB_8888);
		Paint paint = new Paint();
		paint.setColor(color);
		paint.setAlpha(128);
		paint.setAntiAlias(true);
		Canvas canvas = new Canvas(resultBitmap);
		canvas.drawCircle(radius, radius, radius, paint);
		canvas.drawBitmap(bitmap, deltaRadius, deltaRadius, null);
		return resultBitmap;
	}

	public final static Bitmap getRound(int argb, int dia) {
		float radius = dia / 2;
		Bitmap resultBitmap = Bitmap.createBitmap(dia, dia, Config.ARGB_8888);
		Paint paint = new Paint();
		paint.setColor(argb);
		paint.setAntiAlias(true);
		Canvas canvas = new Canvas(resultBitmap);
		canvas.drawCircle(radius, radius, radius, paint);
		return resultBitmap;
	}

	public final static Bitmap stretch(Bitmap bitmap, int height) {
		if (bitmap == null) {
			return null;
		}
		if (height <= bitmap.getHeight()) {
			return bitmap;
		}
		int w = bitmap.getWidth();
		int h = bitmap.getHeight();
		int tmpHeight = 1;
		Bitmap resultBitmap = Bitmap.createBitmap(w, height, Config.ARGB_8888);
		Canvas mCanvas = new Canvas(resultBitmap);
		mCanvas.drawBitmap(bitmap, 0, 0, null);
		Bitmap tmp = Bitmap
				.createBitmap(bitmap, 0, h - tmpHeight, w, tmpHeight);
		bitmap.recycle();
		for (int index = 0; index < (height - h) / tmpHeight; index++) {
			mCanvas.drawBitmap(tmp, 0, h + index * tmpHeight, null);
		}
		tmp.recycle();
		return resultBitmap;
	}

	public static Bitmap overlay(final Bitmap bitmap) {
		if (bitmap == null) {
			return null;
		}
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		if (width == 0 || height == 0) {
			return null;
		}
		float rota = 345;
		double radians = Math.toRadians(rota);
		/* after rotate , the new width is the original width */
		int rotateWidth = (int) (width / (Math.abs(Math.sin(radians)) + Math
				.abs(Math.cos(radians))));
		int delta = (width - rotateWidth) / 2;

		// result
		Bitmap result = Bitmap.createBitmap(width, width, Config.ARGB_8888);
		Canvas canvas = new Canvas(result);
		// foreground
		Bitmap foreBmp = resizeImage(bitmap, rotateWidth, rotateWidth, true);
		// rotate
		Matrix matrix = new Matrix();
		matrix.postRotate(rota);
		Bitmap rotate = Bitmap.createBitmap(foreBmp, 0, 0, rotateWidth,
				rotateWidth, matrix, true);
		canvas.drawBitmap(rotate, 0, 0, null);
		canvas.drawBitmap(foreBmp, delta, delta, null);
		recycle(bitmap, rotate, foreBmp);
		return result;
	}

	public static Bitmap drawLayoutDropShadow(Bitmap bitmap) {
		BlurMaskFilter blurFilter = new BlurMaskFilter(2,
				BlurMaskFilter.Blur.OUTER);
		Paint shadowPaint = new Paint();
		shadowPaint.setMaskFilter(blurFilter);
		int[] offsetXY = { 5, 5 };
		Bitmap shadowBitmap = bitmap.extractAlpha(shadowPaint, offsetXY);
		Bitmap shadowImage32 = shadowBitmap.copy(Bitmap.Config.ARGB_8888, true);
		Canvas c = new Canvas(shadowImage32);
		c.drawBitmap(bitmap, 0, 0, null);
		return shadowImage32;
	}

	public static Bitmap blur(Bitmap bmp) {
		int iterations = 1;
		int radius = 8;
		int width = bmp.getWidth();
		int height = bmp.getHeight();
		int[] inPixels = new int[width * height];
		int[] outPixels = new int[width * height];
		Bitmap bitmap = Bitmap.createBitmap(width, height,
				Bitmap.Config.ARGB_8888);
		bmp.getPixels(inPixels, 0, width, 0, 0, width, height);
		for (int i = 0; i < iterations; i++) {
			blur(inPixels, outPixels, width, height, radius);
			blur(outPixels, inPixels, height, width, radius);
		}
		bitmap.setPixels(inPixels, 0, width, 0, 0, width, height);
		return bitmap;
	}

	private static void blur(int[] in, int[] out, int width, int height,
			int radius) {
		int widthMinus1 = width - 1;
		int tableSize = 2 * radius + 1;
		int divide[] = new int[256 * tableSize];

		for (int index = 0; index < 256 * tableSize; index++) {
			divide[index] = index / tableSize;
		}

		int inIndex = 0;

		for (int y = 0; y < height; y++) {
			int outIndex = y;
			int ta = 0, tr = 0, tg = 0, tb = 0;

			for (int i = -radius; i <= radius; i++) {
				int rgb = in[inIndex + clamp(i, 0, width - 1)];
				ta += (rgb >> 24) & 0xff;
				tr += (rgb >> 16) & 0xff;
				tg += (rgb >> 8) & 0xff;
				tb += rgb & 0xff;
			}

			for (int x = 0; x < width; x++) {
				out[outIndex] = (divide[ta] << 24) | (divide[tr] << 16)
						| (divide[tg] << 8) | divide[tb];

				int i1 = x + radius + 1;
				if (i1 > widthMinus1)
					i1 = widthMinus1;
				int i2 = x - radius;
				if (i2 < 0)
					i2 = 0;
				int rgb1 = in[inIndex + i1];
				int rgb2 = in[inIndex + i2];

				ta += ((rgb1 >> 24) & 0xff) - ((rgb2 >> 24) & 0xff);
				tr += ((rgb1 & 0xff0000) - (rgb2 & 0xff0000)) >> 16;
				tg += ((rgb1 & 0xff00) - (rgb2 & 0xff00)) >> 8;
				tb += (rgb1 & 0xff) - (rgb2 & 0xff);
				outIndex += height;
			}
			inIndex += width;
		}
	}

	private static int clamp(int x, int a, int b) {
		return (x < a) ? a : (x > b) ? b : x;
	}

	private static int sampleSize(int size, int target) {
		int sample = 1;
		for (int index = 0; index < 10; index++) {
			if (size < target * 2) {
				break;
			}
			size = size / 2;
			sample = sample * 2;
		}
		return sample;
	}

	private final static void recycle(Bitmap... bitmaps) {
		if (bitmaps == null || bitmaps.length == 0) {
			return;
		}
		for (Bitmap bitmap : bitmaps) {
			if (bitmap == null) {
				continue;
			}
			if (!bitmap.isRecycled()) {
				bitmap.recycle();
				continue;
			}
			bitmap = null;
		}
		System.gc();
	}
}
